#ifndef NETQUEUE_H_
#define NETQUEUE_H_

#include <assert.h>
#include <errno.h>

#include "Synchronized.hh"

#include <lagniappe/RCon.h>
#include <lagniappe/SerialRCon.h>

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <netinet/in.h>


// just define the IP address as a constant up here as you can change it for all instances
// of the class on each machine

const unsigned int PORT_BASE = 8000;
const unsigned int BUFFSIZE = 8192;

#define DEST_ADDR "129.116.113.86"

typedef int socket_t;

template<int I>
class netQueue : public Synchronized
{
private:
  socket_t listener;
  unsigned int port;
  fd_set openSet;
  socket_t maxFD;
  bool sysReady;
  void isSysReady() {
    Synchronized::Scope mutex(this);
    while(!sysReady) {
      wait();
    }
  }
  void makeSysReady() {
    Synchronized::Scope mutex(this);
    sysReady = true;
  }
public:
  netQueue() : port(PORT_BASE + I), sysReady(false) {
    int yes = 1;
    sockaddr_in my_addr;
    listener = socket(PF_INET, SOCK_STREAM, 0);
    if(listener == -1) {
      std::cerr << "Problem opening socket:" << strerror(errno) << std::endl;
      exit(1);
    }
    if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
      std::cerr << "Problem setting socket options: " << strerror(errno) << std::endl;
      exit(1);
    }
    
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(port);
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    memset(my_addr.sin_zero, '\0', sizeof my_addr.sin_zero);
    
    unsigned int bindReturn = bind(listener, (struct sockaddr *)&my_addr, sizeof(my_addr));
    if(bindReturn != 0) {
      close(listener);
      std::cerr << "Problem with binding" << strerror(errno) << std::endl;
      exit(1);
    }
    FD_SET(listener, &openSet);
    maxFD = listener;
    if(listen(listener, 500) == -1) {
      std::cerr << "Listening error: " << strerror(errno) << std::endl;
      exit(1);
    }
    makeSysReady();
  }
  void push(lagniappe::RCon *rc) {
    isSysReady();
    lagniappe::SerialRCon * sRCon = new lagniappe::SerialRCon(rc);
    socket_t sock;
    sockaddr_in dest_addr;   // will hold the destination addr

    sock = socket(PF_INET, SOCK_STREAM, 0); // do some error checking!

    dest_addr.sin_family = AF_INET;          // host byte order
    dest_addr.sin_port = htons(port);   // short, network byte order
    dest_addr.sin_addr.s_addr = inet_addr(DEST_ADDR);
    memset(dest_addr.sin_zero, '\0', sizeof dest_addr.sin_zero);

    std::cerr << "Port: " << port << std::endl;

    // don't forget to error check the connect()!
    if(connect(sock, (sockaddr *)&dest_addr, sizeof dest_addr) < 0) {
      std::cerr << "Problem connecting: " << strerror(errno) << std::endl;
      exit(1);
    }
     
    int bytes_sent;
    if((bytes_sent = send(sock, (void *)sRCon, sizeof(lagniappe::SerialRCon), 0)) < 0) {
      std::cerr << "Problem sending: " << strerror(errno) << std::endl;
      exit(1);
    }
    std::cerr << "Bytes sent: (" << sizeof(lagniappe::SerialRCon) << ") " << bytes_sent << std::endl;
    delete rc;
    delete sRCon;
  }
  lagniappe::RCon * pop() {
    isSysReady();
    fd_set readSet = openSet;
    sockaddr_in remoteAddr;
    socklen_t addrlen;
    lagniappe::SerialRCon * sRCon = NULL;
    timeval sel_time;
    sel_time.tv_sec = 0;
    sel_time.tv_usec = 10;
    while(sRCon == NULL) {
      if(select(maxFD+1, &readSet, NULL, NULL, &sel_time) == -1) {
	if(errno != EINTR) {
	  std::cerr << "Select error: " << strerror(errno) << std::endl;
	  exit(1);
	}
      }
      for(socket_t i = 0; i <= maxFD; ++i) {
	if(FD_ISSET(i, &readSet)) {
	  if (i == listener) {
	    // new connection
	    addrlen = sizeof(remoteAddr);
	    socket_t newSock;
	    if((newSock = accept(listener, NULL, NULL)) == -1) {
	      std::cerr << "Accept error: " << strerror(errno) << std::endl;
	      exit(1);
	    }
	    else {
	      int yes = 1;
	      if (setsockopt(newSock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
		std::cerr << "Problem setting socket options: " << strerror(errno) << std::endl;
		exit(1);
	      }
	      FD_SET(newSock, &openSet);
	      if(newSock > maxFD) {
		maxFD = newSock;
	      }
	      std::cout << "NetRequestDevice: new connection from " << inet_ntoa(remoteAddr.sin_addr) << " on socket " << newSock << std::endl;
	    }
	  }
	  else {
	    // data from an established connection
	    char * buffer = new char[lagniappe::SERIAL_BUFF_SIZE];
	    int numBytes=0;
	    if((numBytes = recv(i, buffer, lagniappe::SERIAL_BUFF_SIZE, 0)) <= 0) {
	      if(numBytes == 0) {
		shutdown(i, SHUT_RDWR);
		if(close(i) < 0) {perror("Close");}
		FD_CLR(i, &openSet);
		delete[] buffer;
	      }
	      else {
		std::cerr << "Receive error: " << strerror(errno) << std::endl;
		delete[] buffer;
		//exit(1);
	      }
	    }
	    else {
	      // process data
	      // actually send out an RCon
	      assert(buffer != NULL);
	      // this could very well be a bug later on
	      sRCon = new lagniappe::SerialRCon();
	      memcpy((void *)sRCon, buffer, numBytes);
	      delete[] buffer;
	    }
	  }
	}
      }
    }
    return sRCon;

  }
  bool empty() {
    isSysReady();
    timeval sel_time;
    sel_time.tv_sec = 0;
    sel_time.tv_usec = 10;
    fd_set readSet = openSet;
    int sel_return;
    if((sel_return = select(maxFD+1, &readSet, NULL, NULL, &sel_time)) == -1) {
      if(errno != EINTR) {
	std::cerr << "Select error: " << strerror(errno) << std::endl;
	exit(1);
      }
    }
    bool retVal = true;
    if(sel_return > 0) {
      std::cerr << "Our first catch of the day..." << std::endl;
      retVal = false;
    }
    return retVal;
  }
};

#endif // NETQUEUE_H_
